import { Component } from './component/Component'
import { Rect } from './component/Rect'
import { Text } from './component/Text'
import { Image } from './component/Image'
import { Path } from './component/Path'
import { Arc } from './component/Arc'
import { Ani } from './ani/Ani'
import { Stage } from './Stage'
import { Renderer } from './Renderer'

export class CanvasRenderer implements Renderer {
  getImageData () {
    return this.canvas.toDataURL('image/png', 0.99)
  }

  canvas: HTMLCanvasElement
  ctx: CanvasRenderingContext2D
  stage: Stage
  constructor (canvas?: HTMLCanvasElement) {
    if (canvas != null) this.setCanvas(canvas)
  }

  clean () {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
  }

  setCanvas (canvas: HTMLCanvasElement) {
    this.canvas = canvas
    const ctx = this.canvas.getContext('2d')
    if (ctx) {
      this.ctx = ctx
      this.ctx.imageSmoothingEnabled = true
    }
  }

  render (ani?: Ani) {
    if (!ani) return
    const sec = this.stage.sec
    const comp = ani.getComponent(sec)
    if (comp instanceof Component) {
      this.renderComp(comp)
    }
  }

  private renderComp (comp: Component | null | undefined) {
    if (!comp) return
    this.ctx.save()
    // render special component props
    if (this.ctx.globalAlpha > 0) {
      // render itself
      // render base component props
      this.renderBase(comp)
      switch (comp.type) {
        case 'Text':
          this.renderText(comp as Text)
          break
        case 'Rect':
          this.renderRect(comp as Rect)
          break
        case 'Image':
          this.renderImage(comp as Image)
          break
        case 'Arc':
          this.renderArc(comp as Arc)
          break
        case 'Line':
          this.renderPath(comp as Path)
          break
      }
    }
    if (comp.children) {
      comp.children.forEach(child => {
        this.renderComp(child)
      })
    }
    this.ctx.restore()
  }

  renderArc (arc: Arc) {
    this.ctx.beginPath()
    this.ctx.arc(
      0,
      0,
      arc.radius,
      arc.startAngle,
      arc.endAngle,
      arc.anticlockwise,
    )
    if (arc.strokeStyle) this.ctx.stroke()
    if (arc.fillStyle) this.ctx.fill()
  }

  renderPath (line: Path) {
    if (!line.path) {
      return
    }
    let path: Path2D
    if (typeof line.path === 'string') {
      path = new Path2D(line.path)
    } else {
      path = line.path
    }
    if (this.ctx.fillStyle) this.ctx.fill(path)
    if (this.ctx.strokeStyle) this.ctx.stroke(path)
  }

  renderClipRect (component: Rect) {
    this.ctx.beginPath()
    this.radiusArea(
      0,
      0,
      component.shape.width,
      component.shape.height,
      component.radius,
    )
    this.ctx.clip()
    this.ctx.closePath()
  }

  renderImage (image: Image) {
    const src = this.stage.resource.images.get(image.src)
    if (src == null) {
      return
    }
    if (image.sliceShape) {
      this.ctx.drawImage(
        image as any,
        image.slicePosition.x,
        image.slicePosition.y,
        image.sliceShape.width,
        image.sliceShape.height,
        0,
        0,
        image.shape.width,
        image.shape.height,
      )
    } else if (image.shape) {
      this.ctx.drawImage(src, 0, 0, image.shape.width, image.shape.height)
    } else {
      this.ctx.drawImage(src, 0, 0)
    }
  }

  renderRect (component: Rect) {
    if (component.clip) {
      this.renderClipRect(component)
    }
    if (!component.radius || component.radius <= 0) {
      this.ctx.fillRect(0, 0, component.shape.width, component.shape.height)
    } else {
      this.fillRadiusRect(
        0,
        0,
        component.shape.width,
        component.shape.height,
        component.radius,
      )
    }
    if (component.strokeStyle) {
      if (!component.radius || component.radius <= 0) {
        this.ctx.strokeRect(
          0,
          0,
          component.shape.width,
          component.shape.height,
        )
      } else {
        this.strokeRadiusRect(
          0,
          0,
          component.shape.width,
          component.shape.height,
          component.radius,
        )
      }
    }
  }

  renderBase (component: Component) {
    let position: { x: number, y: number }
    if (!component.position) {
      if (component.type === 'Text') {
        position = {
          x: this.canvas.width / 2,
          y: this.canvas.height / 2,
        }
      } else {
        position = {
          x: 0,
          y: 0,
        }
      }
    } else {
      position = component.position
    }
    this.ctx.translate(
      position.x - component.center.x,
      position.y - component.center.y,
    )
    if (component.filter) {
      this.ctx.filter = component.filter
    }
    if (component.strokeStyle) {
      this.ctx.strokeStyle = component.strokeStyle
    }
    if (component.fillStyle) {
      this.ctx.fillStyle = component.fillStyle
    }
    if (component.lineWidth) {
      this.ctx.lineWidth = component.lineWidth
    }
    if (component.alpha !== undefined) {
      this.ctx.globalAlpha *= component.alpha
    }
    if (component.scale !== undefined) {
      this.ctx.scale(component.scale.x, component.scale.y)
    }
    if (component.shadow?.enable) {
      this.ctx.shadowBlur = component.shadow?.blur ?? 10
      this.ctx.shadowColor = component.shadow?.color ?? '#000'
    }
  }

  renderText (component: Text) {
    this.prerenderText(component)
    if (component.strokeStyle && component.lineWidth) {
      this.ctx.lineJoin = 'round'
      this.ctx.strokeText(component.text, 0, 0)
    }
    if (component.fillStyle) {
      this.ctx.fillText(component.text, 0, 0)
    }
  }

  prerenderText (component: Text) {
    if (component.textAlign) {
      this.ctx.textAlign = component.textAlign
    }
    if (component.textBaseline) {
      this.ctx.textBaseline = component.textBaseline
    }
    const fontStr = `${component.fontStyle ? component.fontStyle : ''} ${
      component.fontVariant ? component.fontVariant : ''
    } ${component.fontWeight ? component.fontWeight : ''} ${
      component.fontSize ? component.fontSize : 16
    }px ${component.font ? component.font : ''}`
    if (
      component.font ||
      component.fontSize ||
      component.fontWeight ||
      component.fontStyle ||
      component.fontVariant
    ) {
      this.ctx.font = fontStr
    }
  }

  private fillRadiusRect (
    left: number,
    top: number,
    w: number,
    h: number,
    r: number,
  ) {
    this.ctx.beginPath()
    this.radiusArea(left, top, w, h, r)
    this.ctx.closePath()
    this.ctx.fill()
  }

  private strokeRadiusRect (
    left: number,
    top: number,
    w: number,
    h: number,
    r: number,
  ) {
    this.ctx.beginPath()
    this.radiusArea(left, top, w, h, r)
    this.ctx.closePath()
    this.ctx.stroke()
  }

  private radiusArea (
    left: number,
    top: number,
    w: number,
    h: number,
    r: number,
  ) {
    this.ctx.lineWidth = 0
    const pi = Math.PI
    this.ctx.arc(left + r, top + r, r, -pi, -pi / 2)
    this.ctx.arc(left + w - r, top + r, r, -pi / 2, 0)
    this.ctx.arc(left + w - r, top + h - r, r, 0, pi / 2)
    this.ctx.arc(left + r, top + h - r, r, pi / 2, pi)
  }
}
export const canvasRenderer = new CanvasRenderer()
